# coding=utf-8
"""
作者：董新强 
创建时间：2019-2-27
描述：密码验证(兼容django用户密码加密) PBKDF2 + HMAC + SHA256
"""
import base64
import datetime
import hashlib
import hmac
import random
from decimal import Decimal


class Pbkdf2Password():
    algorithm = "pbkdf2_sha256"
    iterations = 120000
    digest = hashlib.sha256

    def encode(self, raw_password):
        length = 12
        allowed_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
        salt = ''.join(random.choice(allowed_chars) for i in range(length))
        return self._encode(raw_password, salt, iterations=self.iterations * random.randint(1, 8))

    def verify(self, password, encoded):
        algorithm, iterations, salt, hash = encoded.split('$', 3)
        assert algorithm == self.algorithm
        encoded_2 = self._encode(password, salt, int(iterations))
        return Pbkdf2Password.constant_time_compare(encoded, encoded_2)

    def _encode(self, password, salt, iterations=None):
        assert password is not None
        assert salt and '$' not in salt
        iterations = iterations or self.iterations
        hash = Pbkdf2Password.pbkdf2(password, salt, iterations, digest=self.digest)
        hash = base64.b64encode(hash).decode('ascii').strip()
        return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)

    @staticmethod
    def pbkdf2(password, salt, iterations, dklen=0, digest=None):
        if digest is None:
            digest = hashlib.sha256
        dklen = dklen or None
        password = Pbkdf2Password.force_bytes(password)
        salt = Pbkdf2Password.force_bytes(salt)
        return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen)

    @staticmethod
    def mask_hash(hash, show=6, char="*"):
        masked = hash[:show]
        masked += char * len(hash[show:])
        return masked

    @staticmethod
    def constant_time_compare(val1, val2):
        return hmac.compare_digest(Pbkdf2Password.force_bytes(val1), Pbkdf2Password.force_bytes(val2))

    @staticmethod
    def is_protected_type(obj):
        return isinstance(obj, (type(None), int, float, Decimal, datetime.datetime, datetime.date, datetime.time,))

    @staticmethod
    def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
        if isinstance(s, bytes):
            if encoding == 'utf-8':
                return s
            else:
                return s.decode('utf-8', errors).encode(encoding, errors)
        if strings_only and Pbkdf2Password.is_protected_type(s):
            return s
        if isinstance(s, memoryview):
            return bytes(s)
        if not isinstance(s, str):
            return str(s).encode(encoding, errors)
        else:
            return s.encode(encoding, errors)


if __name__ == '__main__':
    inst = Pbkdf2Password()
    # for i in range(20):
    #     if not inst.verify('a123456', inst.encode('a123456')):
    #         print('失败')
    # print('成功')
    print(inst.verify('a123456', r'pbkdf2_sha256$120000$HVv6Eu7f9SNi$Il/P95pvv7CTSD2/87PJIeeijefPf0IeUKn+l4vOQFw='))
